Kotlin Summary
Table of Contents
- Before all
- Types
- Conditionals
- Functions
- Anonymous functions
- Null Safety and Exceptions
- Collections
- Class definition
- Object initialization
- Inheritance
- Objects
- Generics
- Extensions
- Java Interoperability
- Corutines
- Functional Programming Basics
- References
1. Before all
What’s Kotlin
- By Jetbrains which created Intellij IDEA
- Targeting JVM and more, interoperatable with Java
- Concise and safe, with many modern features
the REPL(read, evaluate, print, loop)
Install Kotlin commandline tools in macOS:
1 | brew install kotlin |
Execute kotlinc
to start REPL
2. Types
Built-in types
String, Char, Boolean, Int, Double, List<T>, Set<T>, Map<K, V>, Array<T>
Not like Java, Kotlin has no primitives (int, float etc.) hence avoids the boxing between primitives and objects.
However, in byte code generated by Kotlin, primitives are used to improve performance.
3. Conditionals
Conditional operators
Operator | Description |
---|---|
< | Evaluates whether the value on the left is less than the value on the right. |
<= | Evaluates whether the value on the left is less than or equal to the value on the right. |
> | Evaluates whether the value on the left is greater than the value on the right. |
>= | Evaluates whether the value on the left is greater than or equal to the value on the right. |
== | Evaluates whether the value on the left is equal to the value on the right (like equals in Java). |
!= | Evaluates whether the value on the left is not equal to the value on the right. |
=== | Evaluates whether the two instances point to the same reference (like == in Java). |
!== | Evaluates whether the two instances do not point to the same reference. |
4. Functions
Kotlin supports file level functions
1 | fun main() { |
Default value of parameters
1 | fun main() { |
Named function parameters
1 | fun main() { |
Function names in backticks
It’s said this is useful to call Java methods whose names are reserved words in Kotlin, or to name some special methods.
1 | fun `😂`() { } |
Inline functions
Kotlin support inline functions, which is good for performance.
1 | inline fun factorial(n: Int): Int { |
5. Anonymous functions
The complete structure of an anoymous function should be like:
1 | val funcVariable: (ParamType1, ParamType2) -> ReturnType = |
An example of anoymous function:
1 | val numLetters = "Mississippi".count({ letter -> |
1 | /** |
Parameter types are usually omitted when they caould be inferred. Single parameter’s name could be omitted if its type could ba inferred, and we take it
as the parameter name. Unused parameter name could be marked as _
.
Examples:
1 | val numLetters = "Mississippi".count({ |
Note: an anonymous function must be called immediately, assigned to a variable, or be passed as a parameter.
Anonymous functions as parameter is often in a different style when it is required as the last parameter in another function:
1 | // count starting from a specified index. |
Want to declare a function type that returns something like void
in Java? You need the Unit
.
You can also refer a non anonymous function as parameter in call to another function by using ::
. But I don’t think this is common.
1 | fun isS(char: Char): Boolean { |
6. Null Safety and Exceptions
For nullable variables, there are some common solutions to handle potential null pointer exceptions.
Safe call operator:
1 | var beverage = readLine()?.capitalize() // the beverage would be String?. |
Nonnull assertion:
1 | var beverage = readLine()!!.capitalize() // only if you're really confident |
Check before use:
1 | val result = readLine() |
However, check before use isn’t always safe, e.g. a field of an object might be assigned null by another thread between your null checking and usage of the field. For such case, you would need a local variable to get rid of this risk:
1 | val localVar = a.b |
Elvis operator:
1 | var beverage = readLine()?.capitalize() ?: "" // beverage would be String. |
Exceptions in Kotlin
Kotlin doesn’t require handling of those checked Exceptions of Java. Therefore you can do IO operations without try-catch and exception declaration in the method signature.
7. Collections
destructuring
1 | val (type, name, price) = aList // fetch the leading 3 elements from a list. |
protection of immutable list is not valid at runtime
1 | val a = listOf(1, 2, 3) |
8. Class definition
Visibility modifiers of class members
Modifier | Description |
---|---|
public (default) | The function or property will be accessible by code outside of the class. By default, functions and properties without a visibility modifier are public. |
private | The function or property will be accessible only within the same class. |
protected | The function or property will be accessible only within the same class or its subclass. |
internal | The function or property will be accessible within the same module. |
Class fields
For class fields, default getter
is available for val
and var
fields. But setter
is only for var
fields. Yet we can override the default setter
and getter
:
1 | class X(_name: String, _age: Int) { |
You can override the visibility of a setter to make it less permissive then field’s visibility. E.g., you want to make a field readable everywhere but only writable in the file:
1 | class X(_age: Int) { |
Computed fields
Has getter or/and setter but no real fields to store values.
1 | class Dice() { |
9. Object initialization
The primary contructor
Add parameter list inside a pair of parenthesis after the class name, make it looks like a method. For fields using default getter and setter, you can declare them inside the parameter list by adding var
or val
:
1 | // visibility modifier and default values are also available |
secondary constructor
Must call the primary constructor or another secondary constructor(of course no cycle call)
1 | class X(val name: String, private var age: Int = 0) { |
Apart from default parameter values, named parameters are also available for constructors.
Lazy initialization
Only initialize a field when it’s accessed (by the getter)
1 | class X { |
10. Inheritance
In Java, we only override methods. But in Kotlin, both class functions and fields could be overridden. Also, in Kotlin, classes, functions and fields must be marked open
explicity to make them inheritable or able to be overridden.
1 | open class Room(val name: String) { |
Functions are final by default unless they are inherited from an open class. To prevent inherited functions from being overriden again, we can mark it final
.
In Kotlin’s inheritance, we always have:
However, when compiled into bytecode, the class Room doesn’t inherits the class Any in JVM rule.
11. Objects
singleton by object
keyword
1 | fun main() { |
Anonymous inner class
In java, we often implement interfaces or extend simple classes as anonymous inner class, such as click listeners and view adapters.
In Kotlin, they could be divided into 2 categories. For interfaces with only one method and declared in Java, we can use a format called Lambda:
1 | generateButton.setOnClickListener { view -> |
1 | // I.java |
For other cases, the format is like this:
1 | listView.adapter = object : BaseAdapter() { // call the constructor here. |
Nested classes by default cannot access members of the outer class, unless they are marked inner:
1 | class Outer { |
Data classes
Data classes are suitable for model, that’s what the data
means.
1 | data class Coordinate(var x: Int, var y: Int) { |
when use the data classes:
Implementation of
toString
,equal
andcopy
regarding the fields declared in the primary constructor;Must have a primary constructor whose all parameters are marked with
val
orvar
;Cannot be
abstract
,open
, orinner
.
Operator overloading
1 | data class Coordinate(var x: Int, var y: Int) { |
1 | object Fact { |
12 Generics
1 | open class Book(val name: String) |
1 | val list1: List<Book> = listOf<Novel>(Novel("A"), Novel("B")) |
Actually the List
interface is like this:
1 | public interface List<out E> : Collection<E> { |
Here out
means: any fields of a List whih the generic type E
must be val
. Thus E
is a type only for readable fields.
On the other hand, there is in
, which means a generic type is only for writable variables:
1 | class A<in T> { // no fields of type T can be available to outside |
The wildcard in Java generics has some similarities with the in
and out
in Kotlin, but they are not equivalent concepts.
13. Extensions
Based on anonymous function and generics, Kotlin brings the extensions, which enable us to extend classes with new functions and fields without inheritance. The Kotlin standard library contains a large numbers of extensions.
The code below
1 | fun count(str: String, startIdx: Int, predicate: (Char) -> Boolean) = |
could be refactored as
1 | // just like we have extended the String class, a final class. |
Extend with fields is also available:
1 | val String.countS |
Like computed properties, extended fields have no backing fields, actually the extended fields has no essential differences with extended methods.
Visibility modifiers are also valid for the extensions.
Extensions on nullable types:
1 | fun <T> T?.chkNull(block1: (T) -> Unit, block2: () -> Unit) { |
Generics in extensions:
1 | fun <T> T.prt() { |
Extensions in Kotlin standard library:
1 | /** |
Usage of the let
:
1 | class Y(val pair: Pair<String, String>?) |
Extension function type:
1 | val greetings: String.() -> Unit = { |
Another extension in Kotlin standard library:
1 | /** |
14. Java Interoperability
Nullity
1 | // in java side, mark non-private fields, return type and parameter type |
Jetbrains created their annotations like this:
While the annotations by Google is like this:
File level Kotlin members
Let’s say we have a kotlin file Hello.kt which is:
1 | // in Hello.kt file |
Kotlin functions with default parameter values
1 | // add this annotation to make it generates several overloaded JVM methods |
Access members of companion object in Java
1 | class H { |
Exceptions
As we said earlier, Kotlin doesn’t require we to try-catch code blocks which may throw checked Java Exceptions or add a throws
declaration in the method signatures. So if a Java method is called with throws
declaration is called in kotlin, we might missed the exceptions warning by the Java method.
For Kotlin methods which would be called by Java and might throw checked exceptions, marked it with annotation:
1 |
|
Referred functions of Kotlin
1 | // in Test.kt file |
1 | public class JavaClass { |
What’s the Function1
:
1 | /** A function that takes 1 argument. */ |
15. Corutines
what are coroutines
coroutines are more lightweight than threads
a thread can schedule between multiple coroutines
a coroutine can switch between threads
async
& launch
the most common ways to create coroutines
1 | public fun CoroutineScope.launch( |
1 | public fun <T> CoroutineScope.async( |
Deferred<T>
is also a job
, only that we can call its await
to fetch an result returned by that coroutine. e.g:
1 | private fun fetchData() { |
suspend
functions
In my oppnion, a function with suspend
means 3 things:
- it might takes a not short period of time to finish it (but not blocking a thread);
- if declared in
kotlinx.coroutines
, then it’s a cancelable point; - might be a schedule point for threads.
The most common suspend
method, delay
, fulfills the 3 conclusions, so does the Deferred.await
method.
the cancelable point
Able to cancel:
1 | fun main() = runBlocking { |
Unable to cancel:
1 | fun main() = runBlocking { |
Note: runBlocking
would be explained later.
Coroutine context
1 | fun main() = runBlocking<Unit> { |
Coroutine scope
scope
is just like variables have scope in most programming languages
1 | fun func() { |
the CoroutineScope
interface
1 | public interface CoroutineScope { |
In kotlin, CoroutineScope
is implemented by AbstractCoroutine
, hence a coroutine means a CoroutineScope
, but not vice versa.
Parent coroutine and child coroutine:
1 | GlobalScope.launch { |
Direct calls of launch
& async
also means children coroutines inherits Dispatchers
from parent unless override by providing a Dispatchers
parameter.
1 | GlobalScope.launch(Dispatchers.Main) { |
Normally, parent finish after all children finish. But there is a extension method of CoroutineScope
naming cancel
. When it’s called, the whole coroutine tree are canceled.
1 | /** |
In kotlin, we have a GlobalScope
which is an object
singleton. Coroutines directly created from GlobalScope
can last long as long as the JVM is on. GlobalScope
is convenient for demo, however we may want coroutines to be canceled when an Activity
is destroyed, for example.
1 | class MyActivity : AppCompatActivity() { |
Now let’s go back to explain runBlocking
. runBlocking
also starts a coroutine from GlobeSope
, only that runBlocking
coroutines prevent JVM from ending.
the coroutineScope(block)
method
1 | suspend fun <R> coroutineScope(block: suspend CoroutineScope.() -> R): R |
We can create a new scope with this coroutineScope
method. The most common usage would be like this:
1 | suspend fun doSomeThing(): Int = coroutineScope { |
In the above code, we created a scope with coroutineScope
. When doSomeThing
is called from another coroutine, the scope we created would inherit context from the outer coroutine scope. More importantly, when the outer scope is cancelled, this scope would also be canceled.
In next section, we would see the coroutineScope
has important role in exception handling.
Exception in coroutines
First, what is a crash in Android app? (excluding ANR).
The simplest and best way to handle exceptions, is catching exceptions locally, i.e. don’t let exceptions be passed between coroutines. Like the following:
1 | suspend fun doSomeThing(): Int = coroutineScope { |
It’s simple, but why best? Because when an exception is not caught inside a coroutine, the exception would mostly cancel the whole coroutine tree.
1 | fun main() { |
However, with the coroutineScope
method, we can do more.
1 | suspend fun doSomeThing(): Int = coroutineScope { |
the CancellationException
It is thrown when a coroutine is canceled, but won’t crash the JVM thread.
just like nothing happen:
1 | fun main() { |
JVM thread would crash:
1 | fun main() { |
CancellationException
seems useless, but it can be caught if you want. Just surround the cancel point inside a coroutine with try-catch.
1 | fun main() { |
Look the the code above, if you don’t throw again the CancellationException
again, the coroutine would continue executing, which usually is abnormal.
the CoroutineExceptionHandler
We can set an CoroutineExceptionHandler
to the root scope or the root coroutine to let the handler handle exceptions throws inside all coroutines.
1 | fun main() { |
1 | // would crash!! |
If a coroutine is created from async
, the await
function would also throw the exception escaped from the coroutine:
1 | fun main() { |
16. Functional Programming Basics
Transforms
A transform function works on the contents of a collection by walking through the collection and transforming each item with a transformer function provided as an argument.
the map
method
1 | /** |
Filters
A filter function accepts a predicate function that checks each element in a collection against a condition and returns either true or false.
the filter
method
1 | /** |
Combines
Combining functions take different collections and merge them into a new one.
the zip
method
1 | /** |
Examples
1 | // get primes |
References
- Kotlin Programming: The Big Nerd Ranch Guide, awailable in the O’Reilly Learning platform.
- Official Kotlin coroutine guide.
- Exceptional Exceptions for Coroutines made easy…?